// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var pass = chrome.test.callbackPass;

// Constants as functions, not to be called until after runTests.
function getURLEchoUserAgent() {
  return getServerURL('echoheader?User-Agent');
}

function getURLSetHeader() {
  return getServerURL('set-header?Foo:%20Bar');
}

function getURLNonUTF8SetHeader() {
  return getServerURL('set-header?Foo%3A%20Bar%3D%FE%D1');
}

function getURLHttpSimpleLoad() {
  return getServerURL('extensions/api_test/webrequest/simpleLoad/a.html');
}
function getURLHttpXHRData() {
  return getServerURL('extensions/api_test/webrequest/xhr/data.json');
}
function getURLHttpScriptPage() {
  return getServerURL('extensions/api_test/webrequest/script/index.html');
}
function getURLHttpScriptJS() {
  return getServerURL('extensions/api_test/webrequest/script/test.js');
}
function getDummyScriptDataURL() {
  let script_text = 'fetch(\'./fetch\');\n';
  // Make the script larger than 1K to check the behavior of code cache
  // generation. See: crbug.com/782793.
  for (let i = 0; i < 100; ++i) {
    script_text += '/**********/\n';
  }
  return 'data:text/javascript;base64,' +
          btoa(unescape(encodeURIComponent(script_text)));
}
function getURLHttpScriptJSFetchedData() {
  return getServerURL('extensions/api_test/webrequest/script/fetch');
}

function toCharCodes(str) {
  var result = [];
  for (var i = 0; i < str.length; ++i) {
    result[i] = str.charCodeAt(i);
  }
  return result;
}

runTests([
  // Navigates to a page with subresources, with a blocking handler that
  // cancels the page request. The page will not load, and we should not
  // see the subresources.
  function complexLoadCancelled() {
    expect(
      [  // events
        { label: "onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            type: "main_frame",
            url: getURL("complexLoad/b.html"),
            frameUrl: getURL("complexLoad/b.html"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          },
          retval: {cancel: true}
        },
        // Cancelling is considered an error.
        { label: "onErrorOccurred",
          event: "onErrorOccurred",
          details: {
            url: getURL("complexLoad/b.html"),
            fromCache: false,
            error: "net::ERR_BLOCKED_BY_CLIENT",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
      ],
      [  // event order
        ["onBeforeRequest", "onErrorOccurred"]
      ],
      {urls: ["<all_urls>"]},  // filter
      ["blocking"]);
    navigateAndWait(getURL("complexLoad/b.html"));
  },

  // Navigates to a page with subresources, with a blocking handler that
  // cancels the page request. The page will not load, and we should not
  // see the subresources.
  function simpleLoadCancelledOnReceiveHeaders() {
    expect(
      [  // events
        { label: "onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            method: "GET",
            type: "main_frame",
            url: getURLHttpSimpleLoad(),
            frameUrl: getURLHttpSimpleLoad(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
          retval: {cancel: false}
        },
        { label: "onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
            // Note: no requestHeaders because we don't ask for them.
          },
        },
        { label: "onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLHttpSimpleLoad(),
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
          retval: {cancel: true}
        },
        // Cancelling is considered an error.
        { label: "onErrorOccurred",
          event: "onErrorOccurred",
          details: {
            url: getURLHttpSimpleLoad(),
            fromCache: false,
            error: "net::ERR_BLOCKED_BY_CLIENT",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
      ],
      [  // event order
        ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders",
         "onHeadersReceived", "onErrorOccurred"]
      ],
      {urls: ["<all_urls>"]},  // filter
      ["blocking"]);
    navigateAndWait(getURLHttpSimpleLoad());
  },

  // Navigates to a page and provides invalid header information. The request
  // should continue as if the headers were not changed.
  function simpleLoadIgnoreOnBeforeSendHeadersInvalidHeaders() {
    expect(
      [  // events
        { label: "onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            method: "GET",
            type: "main_frame",
            url: getURLHttpSimpleLoad(),
            frameUrl: getURLHttpSimpleLoad()
          },
        },
        { label: "onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            requestHeadersValid: true
          },
          retval: {requestHeaders: [{name: "User-Agent"}]}
        },
        // The headers were invalid, so they should not be modified.
        // TODO(robwu): Test whether an error is logged to the console.
        { label: "onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            requestHeadersValid: true
          }
        },
        { label: "onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLHttpSimpleLoad(),
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200
          }
        },
        { label: "onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLHttpSimpleLoad(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK"
          }
        },
        { label: "onCompleted",
          event: "onCompleted",
          details: {
            url: getURLHttpSimpleLoad(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK"
          }
        },
      ],
      [  // event order
        ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders",
         "onHeadersReceived", "onResponseStarted", "onCompleted"]
      ],
      {urls: ["<all_urls>"]},  // filter
      ["blocking", "requestHeaders"]);
    navigateAndWait(getURLHttpSimpleLoad());
  },

  // Navigates to a page and provides invalid header information. The request
  // should continue as if the headers were not changed.
  function simpleLoadIgnoreOnBeforeSendHeadersInvalidResponse() {
    // Exception handling seems to break this test, so disable it.
    // See http://crbug.com/370897. TODO(robwu): Fix me.
    chrome.test.setExceptionHandler(function(){});
    expect(
      [  // events
        { label: "onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            method: "GET",
            type: "main_frame",
            url: getURLHttpSimpleLoad(),
            frameUrl: getURLHttpSimpleLoad(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
        },
        { label: "onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            requestHeadersValid: true,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
          retval: {foo: "bar"}
        },
        // TODO(robwu): Test whether an error is logged to the console.
        { label: "onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            requestHeadersValid: true,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLHttpSimpleLoad(),
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLHttpSimpleLoad(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onCompleted",
          event: "onCompleted",
          details: {
            url: getURLHttpSimpleLoad(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
      ],
      [  // event order
        ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders",
         "onHeadersReceived", "onResponseStarted", "onCompleted"]
      ],
      {urls: ["<all_urls>"]},  // filter
      ["blocking", "requestHeaders"]);
    navigateAndWait(getURLHttpSimpleLoad());
  },

  // Navigates to a page with a blocking handler that redirects to a different
  // page.
  function complexLoadRedirected() {
    expect(
      [  // events
        { label: "onBeforeRequest-1",
          event: "onBeforeRequest",
          details: {
            url: getURL("complexLoad/a.html"),
            frameUrl: getURL("complexLoad/a.html"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          },
          retval: {redirectUrl: getURL("simpleLoad/a.html")}
        },
        { label: "onBeforeRedirect",
          event: "onBeforeRedirect",
          details: {
            url: getURL("complexLoad/a.html"),
            redirectUrl: getURL("simpleLoad/a.html"),
            fromCache: false,
            statusLine: "HTTP/1.1 307 Internal Redirect",
            statusCode: 307,
            initiator: getDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onBeforeRequest-2",
          event: "onBeforeRequest",
          details: {
            url: getURL("simpleLoad/a.html"),
            frameUrl: getURL("simpleLoad/a.html"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          },
        },
        { label: "onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("simpleLoad/a.html"),
            fromCache: false,
            statusCode: 200,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("simpleLoad/a.html"),
            fromCache: false,
            statusCode: 200,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
      ],
      [  // event order
        ["onBeforeRequest-1", "onBeforeRedirect", "onBeforeRequest-2",
         "onResponseStarted", "onCompleted"],
      ],
      {urls: ["<all_urls>"]}, // filter
      ["blocking"]);
    navigateAndWait(getURL("complexLoad/a.html"));
  },

  // Tests redirect of <img crossorigin="anonymous" src="...">
  function crossOriginAnonymousRedirect() {
    testLoadCORSImage("anonymous");
  },

  // Tests redirect of <img crossorigin="use-credentials" src="...">
  function crossOriginCredentialedRedirect() {
    testLoadCORSImage("use-credentials");
  },

  // Loads a testserver page that echoes the User-Agent header that was
  // sent to fetch it. We modify the outgoing User-Agent in
  // onBeforeSendHeaders, so we should see that modified version.
  function modifyRequestHeaders() {
    expect(
      [  // events
        { label: "onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURLEchoUserAgent(),
            frameUrl: getURLEchoUserAgent(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLEchoUserAgent(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
            // Note: no requestHeaders because we don't ask for them.
          },
          retval: {requestHeaders: [{name: "User-Agent", value: "FoobarUA"}]}
        },
        { label: "onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLEchoUserAgent(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLEchoUserAgent(),
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLEchoUserAgent(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onCompleted",
          event: "onCompleted",
          details: {
            url: getURLEchoUserAgent(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
      ],
      [  // event order
        ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders",
         "onHeadersReceived", "onResponseStarted", "onCompleted"]
      ],
      {urls: ["<all_urls>"]}, ["blocking"]);
    // Check the page content for our modified User-Agent string.
    navigateAndWait(getURLEchoUserAgent(), function() {
      chrome.test.listenOnce(chrome.extension.onRequest, function(request) {
        chrome.test.assertTrue(request.pass, "Request header was not set.");
      });
      chrome.tabs.executeScript(tabId,
        {
          code: "chrome.extension.sendRequest(" +
              "{pass: document.body.innerText.indexOf('FoobarUA') >= 0});"
        });
    });
  },

  // Loads a testserver page that echoes the User-Agent header that was
  // sent to fetch it. We modify the outgoing User-Agent in
  // onBeforeSendHeaders, so we should see that modified version.
  // In this version we check whether we can set binary header values.
  function modifyBinaryRequestHeaders() {
    expect(
      [  // events
        { label: "onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURLEchoUserAgent(),
            frameUrl: getURLEchoUserAgent(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLEchoUserAgent(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
            // Note: no requestHeaders because we don't ask for them.
          },
          retval: {requestHeaders: [{name: "User-Agent",
                                     binaryValue: toCharCodes("FoobarUA")}]}
        },
        { label: "onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLEchoUserAgent(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLEchoUserAgent(),
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLEchoUserAgent(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onCompleted",
          event: "onCompleted",
          details: {
            url: getURLEchoUserAgent(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
      ],
      [  // event order
        ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders",
         "onHeadersReceived", "onResponseStarted", "onCompleted"]
      ],
      {urls: ["<all_urls>"]}, ["blocking"]);
    // Check the page content for our modified User-Agent string.
    navigateAndWait(getURLEchoUserAgent(), function() {
      chrome.test.listenOnce(chrome.extension.onRequest, function(request) {
        chrome.test.assertTrue(request.pass, "Request header was not set.");
      });
      chrome.tabs.executeScript(tabId,
        {
          code: "chrome.extension.sendRequest(" +
              "{pass: document.body.innerText.indexOf('FoobarUA') >= 0});"
        });
    });
  },

  // Loads a testserver page that sets a header "Foo: Bar" but removes the
  // header from the response headers so that it is not set.
  function modifyResponseHeaders() {
    expect(
      [  // events
        { label: "a-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURL("simpleLoad/a.html"),
            frameUrl: getURL("simpleLoad/a.html"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "a-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED),
            responseHeadersExist: true
          }
        },
        { label: "a-onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED),
            responseHeadersExist: true
          }
        },
        {
          label: "x-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURLSetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            frameUrl: "unknown frame URL",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        {
          label: "x-onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLSetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "x-onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLSetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        {
          label: "x-onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLSetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            responseHeadersExist: true,
            initiator: getDomain(initiators.WEB_INITIATED)
          },
          retval_function: function(name, details) {
            responseHeaders = details.responseHeaders;
            var found = false;
            for (var i = 0; i < responseHeaders.length; ++i) {
              if (responseHeaders[i].name === "Foo" &&
                  responseHeaders[i].value.indexOf("Bar") != -1) {
                found = true;
                responseHeaders.splice(i);
                break;
              }
            }
            chrome.test.assertTrue(found);
            return {responseHeaders: responseHeaders};
          }
        },
        { label: "x-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLSetHeader(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            initiator: getDomain(initiators.WEB_INITIATED),
            responseHeadersExist: true
          }
        },
        { label: "x-onCompleted",
          event: "onCompleted",
          details: {
            url: getURLSetHeader(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            initiator: getDomain(initiators.WEB_INITIATED),
            responseHeadersExist: true
          }
        },
      ],
      [  // event order
        ["a-onBeforeRequest", "a-onResponseStarted", "a-onCompleted",
         "x-onBeforeRequest", "x-onBeforeSendHeaders", "x-onSendHeaders",
         "x-onHeadersReceived", "x-onResponseStarted", "x-onCompleted"],
      ],
      {urls: ["<all_urls>"]}, ["blocking", "responseHeaders"]);
    navigateAndWait(getURL("simpleLoad/a.html"), function() {
      // Check that the header will be removed from the XMLHttpRequest.
      var xhr = new XMLHttpRequest();
      xhr.open("GET", getURLSetHeader(), true);
      xhr.onload = pass(function() {
        chrome.test.assertTrue(xhr.getResponseHeader('Foo') == null,
            'Header was not removed.');
      });
      xhr.onerror = function() {
        chrome.test.fail();
      }
      xhr.send();
    });
  },

  // Loads a testserver page that sets a header "Foo: BarU+FDD1" which is not a
  // valid UTF-8 code point. Therefore, it cannot be passed to JavaScript
  // as a normal string.
  function handleNonUTF8InModifyResponseHeaders() {
    expect(
      [  // events
        { label: "a-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURL("simpleLoad/a.html"),
            frameUrl: getURL("simpleLoad/a.html"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "a-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED),
            responseHeadersExist: true
          }
        },
        { label: "a-onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED),
            responseHeadersExist: true
          }
        },
        {
          label: "x-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURLNonUTF8SetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            frameUrl: "unknown frame URL",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        {
          label: "x-onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLNonUTF8SetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "x-onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLNonUTF8SetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        {
          label: "x-onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLNonUTF8SetHeader(),
            tabId: 1,
            type: "xmlhttprequest",
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            responseHeadersExist: true,
            initiator: getDomain(initiators.WEB_INITIATED)
          },
          retval_function: function(name, details) {
            responseHeaders = details.responseHeaders;
            var found = false;
            var expectedValue = [
              "B".charCodeAt(0),
              "a".charCodeAt(0),
              "r".charCodeAt(0),
              0x3D, 0xFE, 0xD1
              ];

            for (var i = 0; i < responseHeaders.length; ++i) {
              if (responseHeaders[i].name === "Foo" &&
                  deepEq(responseHeaders[i].binaryValue, expectedValue)) {
                found = true;
                responseHeaders.splice(i);
                break;
              }
            }
            chrome.test.assertTrue(found);
            return {responseHeaders: responseHeaders};
          }
        },
        { label: "x-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLNonUTF8SetHeader(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            initiator: getDomain(initiators.WEB_INITIATED),
            responseHeadersExist: true
          }
        },
        { label: "x-onCompleted",
          event: "onCompleted",
          details: {
            url: getURLNonUTF8SetHeader(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            initiator: getDomain(initiators.WEB_INITIATED),
            responseHeadersExist: true
          }
        },
      ],
      [  // event order
        ["a-onBeforeRequest", "a-onResponseStarted", "a-onCompleted",
         "x-onBeforeRequest", "x-onBeforeSendHeaders", "x-onSendHeaders",
         "x-onHeadersReceived", "x-onResponseStarted", "x-onCompleted"],
      ],
      {urls: ["<all_urls>"]}, ["blocking", "responseHeaders"]);
    navigateAndWait(getURL("simpleLoad/a.html"), function() {
      // Check that the header will be removed from the XMLHttpRequest.
      var xhr = new XMLHttpRequest();
      xhr.open("GET", getURLNonUTF8SetHeader(), true);
      xhr.onload = pass(function() {
        chrome.test.assertTrue(xhr.getResponseHeader('Foo') == null,
            'Header was not removed.');
      });
      xhr.onerror = function() {
        chrome.test.fail();
      }
      xhr.send();
    });
  },

  // Navigates to a page with a blocking handler that redirects to a different
  // non-http page during onHeadersReceived. The requested page should not be
  // loaded, and the redirect should succeed.
  function simpleLoadRedirectOnReceiveHeaders() {
    expect(
      [  // events
        { label: "onBeforeRequest-1",
          event: "onBeforeRequest",
          details: {
            method: "GET",
            type: "main_frame",
            url: getURLHttpSimpleLoad(),
            frameUrl: getURLHttpSimpleLoad(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
        },
        { label: "onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
            // Note: no requestHeaders because we don't ask for them.
          },
        },
        { label: "onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLHttpSimpleLoad(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLHttpSimpleLoad(),
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
          retval: {redirectUrl: getURL("simpleLoad/a.html")}
        },
        { label: "onBeforeRedirect",
          event: "onBeforeRedirect",
          details: {
            url: getURLHttpSimpleLoad(),
            redirectUrl: getURL("simpleLoad/a.html"),
            statusLine: "HTTP/1.1 302 Found",
            statusCode: 302,
            fromCache: false,
            ip: "127.0.0.1",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onBeforeRequest-2",
          event: "onBeforeRequest",
          details: {
            url: getURL("simpleLoad/a.html"),
            frameUrl: getURL("simpleLoad/a.html"),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
        },
        { label: "onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("simpleLoad/a.html"),
            fromCache: false,
            statusCode: 200,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("simpleLoad/a.html"),
            fromCache: false,
            statusCode: 200,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
      ],
      [  // event order
        ["onBeforeRequest-1", "onBeforeSendHeaders", "onSendHeaders",
         "onHeadersReceived", "onBeforeRedirect", "onBeforeRequest-2",
         "onResponseStarted", "onCompleted"]
      ],
      {urls: ["<all_urls>"]},  // filter
      ["blocking"]);
    navigateAndWait(getURLHttpSimpleLoad());
  },

  // Checks that synchronous XHR requests from ourself are invisible to
  // blocking handlers.
  function syncXhrsFromOurselfAreInvisible() {
    expect(
      [  // events
        { label: "a-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURL("simpleLoad/a.html"),
            frameUrl: getURL("simpleLoad/a.html"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "a-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "a-onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        // We do not see onBeforeRequest for the XHR request here because it
        // is handled by a blocking handler.
        { label: "x-onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLHttpXHRData(),
            tabId: 1,
            type: "xmlhttprequest",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "x-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLHttpXHRData(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            initiator: getDomain(initiators.WEB_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "x-onCompleted",
          event: "onCompleted",
          details: {
            url: getURLHttpXHRData(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            initiator: getDomain(initiators.WEB_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "b-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURL("complexLoad/b.jpg"),
            frameUrl: getURL("complexLoad/b.jpg"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "b-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("complexLoad/b.jpg"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "b-onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("complexLoad/b.jpg"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
      ],
      [  // event order
        ["a-onBeforeRequest", "a-onResponseStarted", "a-onCompleted",
         "x-onSendHeaders", "x-onResponseStarted", "x-onCompleted",
         "b-onBeforeRequest", "b-onResponseStarted", "b-onCompleted"]
      ],
      {urls: ["<all_urls>"]}, ["blocking"]);
    // Check the page content for our modified User-Agent string.
    navigateAndWait(getURL("simpleLoad/a.html"), function() {
        var req = new XMLHttpRequest();
        var asynchronous = false;
        req.open("GET", getURLHttpXHRData(), asynchronous);
        req.send(null);
        navigateAndWait(getURL("complexLoad/b.jpg"));
    });
  },

  // Checks that asynchronous XHR requests from ourself are visible to
  // blocking handlers.
  function asyncXhrsFromOurselfAreVisible() {
    expect(
      [  // events
        { label: "a-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURL("simpleLoad/a.html"),
            frameUrl: getURL("simpleLoad/a.html"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "a-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "a-onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("simpleLoad/a.html"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        {
          label: "x-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURLHttpXHRData(),
            tabId: 1,
            type: "xmlhttprequest",
            frameUrl: "unknown frame URL",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        {
          label: "x-onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLHttpXHRData(),
            tabId: 1,
            type: "xmlhttprequest",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "x-onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLHttpXHRData(),
            tabId: 1,
            type: "xmlhttprequest",
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "x-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLHttpXHRData(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            // Request to chrome-extension:// url has no IP.
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        {
          label: "x-onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLHttpXHRData(),
            tabId: 1,
            type: "xmlhttprequest",
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "x-onCompleted",
          event: "onCompleted",
          details: {
            url: getURLHttpXHRData(),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            tabId: 1,
            type: "xmlhttprequest",
            ip: "127.0.0.1",
            // Request to chrome-extension:// url has no IP.
            initiator: getDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "b-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            url: getURL("complexLoad/b.jpg"),
            frameUrl: getURL("complexLoad/b.jpg"),
            initiator: getDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "b-onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURL("complexLoad/b.jpg"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
        { label: "b-onCompleted",
          event: "onCompleted",
          details: {
            url: getURL("complexLoad/b.jpg"),
            statusCode: 200,
            fromCache: false,
            statusLine: "HTTP/1.1 200 OK",
            initiator: getDomain(initiators.BROWSER_INITIATED)
            // Request to chrome-extension:// url has no IP.
          }
        },
      ],
      [  // event order
        ["a-onBeforeRequest", "a-onResponseStarted", "a-onCompleted",
         "x-onBeforeRequest", "x-onBeforeSendHeaders", "x-onSendHeaders",
         "x-onHeadersReceived", "x-onResponseStarted", "x-onCompleted"],
        ["a-onCompleted", "x-onBeforeRequest",
         "b-onBeforeRequest", "b-onResponseStarted", "b-onCompleted"]
      ],
      {urls: ["<all_urls>"]}, ["blocking"]);
    // Check the page content for our modified User-Agent string.
    navigateAndWait(getURL("simpleLoad/a.html"), function() {
        var req = new XMLHttpRequest();
        var asynchronous = true;
        req.open("GET", getURLHttpXHRData(), asynchronous);
        req.send(null);
        navigateAndWait(getURL("complexLoad/b.jpg"));
    });
  },

  // Checks that the script resource request redirection to data url. And also
  // checks that code cache generation doesn't cause crash (crbug.com/782793).
  function dataUrlJavaScriptExecution() {
    expect(
      [  // events
        { label: "onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            method: "GET",
            type: "main_frame",
            url: getURLHttpScriptPage(),
            frameUrl: getURLHttpScriptPage(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
        },
        { label: "onBeforeSendHeaders",
          event: "onBeforeSendHeaders",
          details: {
            url: getURLHttpScriptPage(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
        },
        { label: "onSendHeaders",
          event: "onSendHeaders",
          details: {
            url: getURLHttpScriptPage(),
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onHeadersReceived",
          event: "onHeadersReceived",
          details: {
            url: getURLHttpScriptPage(),
            statusLine: "HTTP/1.1 200 OK",
            statusCode: 200,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          },
        },
        { label: "onResponseStarted",
          event: "onResponseStarted",
          details: {
            url: getURLHttpScriptPage(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "onCompleted",
          event: "onCompleted",
          details: {
            url: getURLHttpScriptPage(),
            fromCache: false,
            statusCode: 200,
            ip: "127.0.0.1",
            statusLine: "HTTP/1.1 200 OK",
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        { label: "script-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            method: "GET",
            type: "script",
            url: getURLHttpScriptJS(),
            frameUrl: getURLHttpScriptPage(),
            initiator: getServerDomain(initiators.WEB_INITIATED)
          },
          retval: {
            redirectUrl: getDummyScriptDataURL()
          },
        },
        { label: "script-onBeforeRedirect",
          event: "onBeforeRedirect",
          details: {
            url: getURLHttpScriptJS(),
            redirectUrl: getDummyScriptDataURL(),
            fromCache: false,
            statusLine: "HTTP/1.1 307 Internal Redirect",
            statusCode: 307,
            type: "script",
            initiator: getServerDomain(initiators.WEB_INITIATED)
          }
        },
        { label: "data-onBeforeRequest",
          event: "onBeforeRequest",
          details: {
            method: "GET",
            type: "xmlhttprequest",
            url: getURLHttpScriptJSFetchedData(),
            frameUrl: getURLHttpScriptPage(),
            initiator: getServerDomain(initiators.WEB_INITIATED),
          },
          retval: {cancel: true}
        },
        // Cancelling is considered an error.
        { label: "data-onErrorOccurred",
          event: "onErrorOccurred",
          details: {
            url: getURLHttpScriptJSFetchedData(),
            fromCache: false,
            type: "xmlhttprequest",
            error: "net::ERR_BLOCKED_BY_CLIENT",
            initiator: getServerDomain(initiators.WEB_INITIATED)
          }
        },
      ],
      [  // event order
        ["onBeforeRequest", "onBeforeSendHeaders", "onSendHeaders",
         "onHeadersReceived", "onResponseStarted", "onCompleted"],
        ["script-onBeforeRequest", "script-onBeforeRedirect"],
        ["data-onBeforeRequest", "data-onErrorOccurred"],
      ],
      {urls: ["<all_urls>"]},  // filter
      ["blocking"]);
    navigateAndWait(getURLHttpScriptPage());
  },
]);


// This helper verifies that extensions can successfully redirect resources even
// if cross-origin access control is in effect via the crossorigin attribute.
// Used by crossOriginAnonymousRedirect and crossOriginCredentialedRedirect.
function testLoadCORSImage(crossOriginAttributeValue) {
  // (Non-existent) image URL, with random query string to bust the cache.
  var requestedUrl = getServerURL("cors/intercepted_by_extension.gif?" +
                                  Math.random(), "original.tld");
  var frameUrl = getServerURL(
      "extensions/api_test/webrequest/cors/load_image.html?" +
      "crossOrigin=" + crossOriginAttributeValue +
      "&src=" + encodeURIComponent(requestedUrl));
  var redirectTarget = getServerURL(
      "extensions/api_test/webrequest/cors/redirect_target.gif", "domain.tld");
  var initiator = getServerDomain(initiators.WEB_INITIATED);
  expect(
    [  // events
      { label: "onBeforeRequest-1",
        event: "onBeforeRequest",
        details: {
          type: "image",
          url: requestedUrl,
          // Frame URL unavailable because requests are filtered by type=image.
          frameUrl: "unknown frame URL",
          initiator: initiator
        },
        retval: {redirectUrl: redirectTarget}
      },
      { label: "onBeforeRedirect",
        event: "onBeforeRedirect",
        details: {
          type: "image",
          url: requestedUrl,
          redirectUrl: redirectTarget,
          statusLine: "HTTP/1.1 307 Internal Redirect",
          statusCode: 307,
          fromCache: false,
          initiator: initiator
        }
      },
      { label: "onBeforeRequest-2",
        event: "onBeforeRequest",
        details: {
          type: "image",
          url: redirectTarget,
          // Frame URL unavailable because requests are filtered by type=image.
          frameUrl: "unknown frame URL",
          initiator: initiator
        },
      },
      {
        label: "onBeforeSendHeaders",
        event: "onBeforeSendHeaders",
        details: {
          type: "image",
          url: redirectTarget,
          initiator: initiator
        }
      },
      {
        label: "onSendHeaders",
        event: "onSendHeaders",
        details: {
          type: "image",
          url: redirectTarget,
          initiator: initiator
        }
      },
      {
        label: "onHeadersReceived",
        event: "onHeadersReceived",
        details: {
          type: "image",
          url: redirectTarget,
          statusLine: "HTTP/1.1 200 OK",
          statusCode: 200,
          initiator: initiator
        }
      },
      { label: "onResponseStarted",
        event: "onResponseStarted",
        details: {
          type: "image",
          url: redirectTarget,
          fromCache: false,
          statusCode: 200,
          ip: "127.0.0.1",
          statusLine: "HTTP/1.1 200 OK",
          initiator: initiator
        }
      },
      { label: "onCompleted",
        event: "onCompleted",
        details: {
          type: "image",
          url: redirectTarget,
          fromCache: false,
          statusCode: 200,
          ip: "127.0.0.1",
          statusLine: "HTTP/1.1 200 OK",
          initiator: initiator
        }
      },
      // After the image loads, the test will load the following URL
      // to signal that the test succeeded.
      {
        label: "onBeforeRequest-3",
        event: "onBeforeRequest",
        details: {
          type: "image",
          url: getServerURL("signal_that_image_loaded_successfully"),
          // Frame URL unavailable because requests are filtered by type=image.
          frameUrl: "unknown frame URL",
          initiator: initiator
        },
        retval: {cancel: true}
      },
      { label: "onErrorOccurred",
        event: "onErrorOccurred",
        details: {
          type: "image",
          url: getServerURL("signal_that_image_loaded_successfully"),
          fromCache: false,
          error: "net::ERR_BLOCKED_BY_CLIENT",
          initiator: initiator
        }
      },
    ],
    [  // event order
      ["onBeforeRequest-1", "onBeforeRedirect", "onBeforeRequest-2",
       "onBeforeSendHeaders", "onSendHeaders", "onHeadersReceived",
       "onResponseStarted", "onCompleted",
       "onBeforeRequest-3", "onErrorOccurred"],
    ],
    {urls: ["<all_urls>"], types: ['image']}, // filter
    ["blocking"]);
  navigateAndWait(frameUrl);
}
